package cacheobj

import (
	"errors"
	"gitee.com/sunny-zhangqiang/zcache/comm"
	"gitee.com/sunny-zhangqiang/zcache/comm/mcache"
	"hash/crc32"
)

var (
	be = comm.BE
)

var (
	errCheckSumNoMatch = errors.New("checksum no match")
	errFormatErr       = errors.New("format error")
	errShortErr        = errors.New("short error")
)

// CacheObjectEncode
// KeyNo: 				01
// VaryHeaderNo:		02
// VaryListNo: 			03
// UnsuitedVaryNo:		04
// DocNo:				05
func CacheObjectEncode(obj *CacheObject) []byte {
	size := 4 // checksum

	if obj.Key != nil {
		size += 4 // Key no
		size += 4 // Key len
		size += len(obj.Key)
	}

	if obj.VaryHeader != nil {
		size += 4 // VaryHeader no
		size += 4 // VaryHeader len
		size += len(obj.VaryHeader)
	}

	if obj.VaryList != nil {
		size += 4 // VaryList no
		size += 4 // VaryList elements
		for _, v := range obj.VaryList {
			size += 4 // v len
			size += len(v)
		}
	}

	if obj.UnsuitedVary != nil {
		size += 4 // UnsuitedVary no
		size += 4 // UnsuitedVary elements
		for k, v := range obj.UnsuitedVary {
			size += 4 // k len
			size += len(k)
			size += 4 // v len
			size += len(v)
		}
	}

	var docSize int
	if obj.Doc != nil {
		size += 4 // Doc no
		size += 4 // Doc len
		docSize = calculateDocumentEncodeSize(obj.Doc)
		size += docSize
	}

	buf := mcache.Malloc(size)

	offset := 4 // checksum

	if obj.Key != nil {
		be.PutUint32(buf[offset:offset+4], 1)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(obj.Key)))
		offset += 4
		copy(buf[offset:offset+len(obj.Key)], obj.Key)
		offset += len(obj.Key)
	}

	if obj.VaryHeader != nil {
		be.PutUint32(buf[offset:offset+4], 2)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(obj.VaryHeader)))
		offset += 4
		copy(buf[offset:offset+len(obj.VaryHeader)], obj.VaryHeader)
		offset += len(obj.VaryHeader)
	}

	if obj.VaryList != nil {
		be.PutUint32(buf[offset:offset+4], 3)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(obj.VaryList)))
		offset += 4
		for _, v := range obj.VaryList {
			be.PutUint32(buf[offset:offset+4], uint32(len(v)))
			offset += 4
			copy(buf[offset:offset+len(v)], v)
			offset += len(v)
		}
	}

	if obj.UnsuitedVary != nil {
		be.PutUint32(buf[offset:offset+4], 4)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(obj.UnsuitedVary)))
		offset += 4
		for k, v := range obj.UnsuitedVary {
			be.PutUint32(buf[offset:offset+4], uint32(len(k)))
			offset += 4
			copy(buf[offset:offset+len(k)], k)
			offset += len(k)
			be.PutUint32(buf[offset:offset+4], uint32(len(v)))
			offset += 4
			copy(buf[offset:offset+len(v)], v)
			offset += len(v)
		}
	}

	if obj.Doc != nil {
		be.PutUint32(buf[offset:offset+4], 5)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(docSize))
		offset += 4
		documentEncode(obj.Doc, buf[offset:offset+docSize])
		offset += docSize
	}

	be.PutUint32(buf[:4], crc32.ChecksumIEEE(buf[4:]))

	return buf
}

func CacheObjectDecode(buf []byte) (obj *CacheObject, err error) {
	size := len(buf)
	offset := 0

	if 4 > size-offset {
		return nil, errShortErr
	}
	checksum := be.Uint32(buf[:4])
	if checksum != crc32.ChecksumIEEE(buf[4:]) {
		return nil, errCheckSumNoMatch
	}
	offset += 4

	obj = &CacheObject{}
	if 4 > size-offset {
		return nil, errShortErr
	}
	no := be.Uint32(buf[offset : offset+4])
	if no == 1 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		len := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if len > size-offset {
			return nil, errShortErr
		}
		obj.Key = buf[offset : offset+len]
		offset += len
	}

	if 4 > size-offset {
		return obj, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 2 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		len := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if len > size-offset {
			return nil, errShortErr
		}
		obj.VaryHeader = buf[offset : offset+len]
		offset += len
	}

	if 4 > size-offset {
		return obj, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 3 {
		offset += 4

		obj.VaryList = make([][]byte, 0)

		if 4 > size-offset {
			return nil, errShortErr
		}
		n := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		for i := 0; i < n; i++ {
			if 4 > size-offset {
				return nil, errShortErr
			}
			len := int(be.Uint32(buf[offset : offset+4]))
			offset += 4

			if len > size-offset {
				return nil, errShortErr
			}
			v := buf[offset : offset+len]
			offset += len
			obj.VaryList = append(obj.VaryList, v)
		}
	}

	if 4 > size-offset {
		return obj, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 4 {
		offset += 4

		obj.UnsuitedVary = make(map[string]string)

		if 4 > size-offset {
			return nil, errShortErr
		}
		n := be.Uint32(buf[offset : offset+4])
		offset += 4
		for i := 0; i < int(n); i++ {
			if 4 > size-offset {
				return nil, errShortErr
			}
			klen := int(be.Uint32(buf[offset : offset+4]))
			offset += 4

			if klen > size-offset {
				return nil, errShortErr
			}
			key := string(buf[offset : offset+klen])
			offset += klen

			if 4 > size-offset {
				return nil, errShortErr
			}
			vlen := int(be.Uint32(buf[offset : offset+4]))
			offset += 4

			value := string(buf[offset : offset+vlen])
			offset += vlen

			obj.UnsuitedVary[key] = value
		}
	}

	if 4 > size-offset {
		return obj, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 5 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		dlen := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if dlen > size-offset {
			return nil, errShortErr
		}
		doc, err := DocumentDecode(buf[offset : offset+dlen])
		if err != nil {
			return nil, err
		}
		obj.Doc = doc
		offset += dlen
	}

	if len(buf) != offset {
		return nil, errShortErr
	}

	return obj, nil
}

// DocumentEncode
// KeyNo: 			01
// ReqTimeNo: 		02
// RespTimeNo: 		03
// RespCodeNo: 		04
// ContentLengthNo: 04
// RespHeaderNo: 	05
// BodyNo: 			06

func calculateDocumentEncodeSize(doc *Document) int {
	size := 4 // checksum

	if doc.Key != nil {
		size += 4 // Key no
		size += 4 // Key len
		size += len(doc.Key)
	}

	size += 4 // ReqTime no
	size += 8 // ReqTime value, int64

	size += 4 // RespTime no
	size += 8 // RespTime value, int64

	size += 4 // RespCode no
	size += 8 // RespCode value, int64

	size += 4 // ContentLength no
	size += 8 // ContentLength value, int64

	if doc.RespHeader != nil {
		size += 4 // RespHeader no
		size += 4 // RespHeader elements
		for k, v := range doc.RespHeader {
			size += 4 // k len
			size += len(k)
			size += 4 // v elements
			for _, vv := range v {
				size += 4 // vv len
				size += len(vv)
			}
		}
	}

	if doc.Body != nil {
		size += 4 // Body no
		size += 4 // Body len
		size += len(doc.Body)
	}

	return size
}

func documentEncode(doc *Document, buf []byte) {
	offset := 4

	if doc.Key != nil {
		be.PutUint32(buf[offset:offset+4], 1)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(doc.Key)))
		offset += 4
		copy(buf[offset:offset+len(doc.Key)], doc.Key)
		offset += len(doc.Key)
	}

	be.PutUint32(buf[offset:offset+4], 2)
	offset += 4
	be.PutUint64(buf[offset:offset+8], uint64(doc.ReqTime))
	offset += 8

	be.PutUint32(buf[offset:offset+4], 3)
	offset += 4
	be.PutUint64(buf[offset:offset+8], uint64(doc.RespTime))
	offset += 8

	be.PutUint32(buf[offset:offset+4], 4)
	offset += 4
	be.PutUint64(buf[offset:offset+8], uint64(doc.RespCode))
	offset += 8

	be.PutUint32(buf[offset:offset+4], 5)
	offset += 4
	be.PutUint64(buf[offset:offset+8], uint64(doc.ContentLength))
	offset += 8

	if doc.RespHeader != nil {
		be.PutUint32(buf[offset:offset+4], 6)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(doc.RespHeader)))
		offset += 4
		for k, v := range doc.RespHeader {
			be.PutUint32(buf[offset:offset+4], uint32(len(k)))
			offset += 4
			copy(buf[offset:offset+len(k)], k)
			offset += len(k)
			be.PutUint32(buf[offset:offset+4], uint32(len(v)))
			offset += 4
			for _, vv := range v {
				be.PutUint32(buf[offset:offset+4], uint32(len(vv)))
				offset += 4
				copy(buf[offset:offset+len(vv)], vv)
				offset += len(vv)
			}
		}
	}

	if doc.Body != nil {
		be.PutUint32(buf[offset:offset+4], 7)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(doc.Body)))
		offset += 4
		copy(buf[offset:offset+len(doc.Body)], doc.Body)
	}

	be.PutUint32(buf[:4], crc32.ChecksumIEEE(buf[4:]))
}

func DocumentEncode(doc *Document) []byte {
	size := calculateDocumentEncodeSize(doc)
	buf := mcache.Malloc(size)
	documentEncode(doc, buf)
	return buf
}

func DocumentDecode(buf []byte) (doc *Document, err error) {
	size := len(buf)
	offset := 0

	if 4 > size-offset {
		return nil, errShortErr
	}
	checksum := be.Uint32(buf[:4])
	if checksum != crc32.ChecksumIEEE(buf[4:]) {
		return nil, errCheckSumNoMatch
	}
	offset += 4

	doc = &Document{}

	if 4 > size-offset {
		return doc, nil
	}
	no := be.Uint32(buf[offset : offset+4])
	if no == 1 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		len := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if len > size-offset {
			return nil, errShortErr
		}
		doc.Key = buf[offset : offset+len]
		offset += len
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 2 {
		offset += 4

		if 8 > size-offset {
			return nil, errShortErr
		}
		reqTime := be.Uint64(buf[offset : offset+8])
		offset += 8
		doc.ReqTime = int64(reqTime)
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 3 {
		offset += 4

		if 8 > size-offset {
			return nil, errShortErr
		}
		respTime := be.Uint64(buf[offset : offset+8])
		offset += 8
		doc.RespTime = int64(respTime)
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 4 {
		offset += 4

		if 8 > size-offset {
			return nil, errShortErr
		}
		respCode := be.Uint64(buf[offset : offset+8])
		offset += 8
		doc.RespCode = int64(respCode)
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 5 {
		offset += 4

		if 8 > size-offset {
			return nil, errShortErr
		}
		cl := be.Uint64(buf[offset : offset+8])
		offset += 8
		doc.ContentLength = int64(cl)
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 6 {
		offset += 4

		doc.RespHeader = make(map[string][]string)

		if 4 > size-offset {
			return nil, errShortErr
		}
		n := be.Uint32(buf[offset : offset+4])
		offset += 4
		for i := 0; i < int(n); i++ {
			if 4 > size-offset {
				return nil, errShortErr
			}
			klen := int(be.Uint32(buf[offset : offset+4]))
			offset += 4

			if klen > size-offset {
				return nil, errShortErr
			}
			key := string(buf[offset : offset+klen])
			offset += klen

			if 4 > size-offset {
				return nil, errShortErr
			}
			nv := be.Uint32(buf[offset : offset+4])
			offset += 4

			value := make([]string, 0)

			for y := 0; y < int(nv); y++ {
				if 4 > size-offset {
					return nil, errShortErr
				}
				vlen := int(be.Uint32(buf[offset : offset+4]))
				offset += 4

				if vlen > size-offset {
					return nil, errShortErr
				}
				v := string(buf[offset : offset+vlen])
				offset += vlen
				value = append(value, v)
			}
			doc.RespHeader[key] = value
		}
	}

	if 4 > size-offset {
		return doc, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 7 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		blen := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if blen > size-offset {
			return nil, errShortErr
		}
		body := buf[offset : offset+blen]
		offset += blen
		doc.Body = body
	}

	if len(buf) != offset {
		return nil, errFormatErr
	}

	return doc, nil
}

func calculateFragmentEncodeSize(frg *Fragment) int {
	size := 4 // checksum

	if frg.Key != nil {
		size += 4 // Key no
		size += 4 // Key len
		size += len(frg.Key)
	}

	if frg.RespHeader != nil {
		size += 4 // RespHeader no
		size += 4 // RespHeader elements
		for k, v := range frg.RespHeader {
			size += 4 // k len
			size += len(k)
			size += 4 // v elements
			for _, vv := range v {
				size += 4 // vv len
				size += len(vv)
			}
		}
	}

	if frg.Body != nil {
		size += 4 // Body no
		size += 4 // Body len
		size += len(frg.Body)
	}

	return size
}

func FragmentEncode(frg *Fragment) []byte {
	size := calculateFragmentEncodeSize(frg)
	buf := mcache.Malloc(size)

	offset := 4
	if frg.Key != nil {
		be.PutUint32(buf[offset:offset+4], 1)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(frg.Key)))
		offset += 4
		copy(buf[offset:offset+len(frg.Key)], frg.Key)
		offset += len(frg.Key)
	}

	if frg.RespHeader != nil {
		be.PutUint32(buf[offset:offset+4], 2)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(frg.RespHeader)))
		offset += 4
		for k, v := range frg.RespHeader {
			be.PutUint32(buf[offset:offset+4], uint32(len(k)))
			offset += 4
			copy(buf[offset:offset+len(k)], k)
			offset += len(k)
			be.PutUint32(buf[offset:offset+4], uint32(len(v)))
			offset += 4
			for _, vv := range v {
				be.PutUint32(buf[offset:offset+4], uint32(len(vv)))
				offset += 4
				copy(buf[offset:offset+len(vv)], vv)
				offset += len(vv)
			}
		}
	}

	if frg.Body != nil {
		be.PutUint32(buf[offset:offset+4], 3)
		offset += 4
		be.PutUint32(buf[offset:offset+4], uint32(len(frg.Body)))
		offset += 4
		copy(buf[offset:offset+len(frg.Body)], frg.Body)
	}

	be.PutUint32(buf[:4], crc32.ChecksumIEEE(buf[4:]))

	return buf
}

func FragmentDecode(buf []byte) (frg *Fragment, err error) {
	size := len(buf)
	offset := 0

	if 4 > size-offset {
		return nil, errShortErr
	}
	checksum := be.Uint32(buf[:4])
	if checksum != crc32.ChecksumIEEE(buf[4:]) {
		return nil, errCheckSumNoMatch
	}
	offset += 4

	frg = &Fragment{}

	if 4 > size-offset {
		return frg, nil
	}
	no := be.Uint32(buf[offset : offset+4])
	if no == 1 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		len := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if len > size-offset {
			return nil, errShortErr
		}
		frg.Key = buf[offset : offset+len]
		offset += len
	}

	if 4 > size-offset {
		return frg, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 2 {
		offset += 4

		frg.RespHeader = make(map[string][]string)

		if 4 > size-offset {
			return nil, errShortErr
		}
		n := be.Uint32(buf[offset : offset+4])
		offset += 4
		for i := 0; i < int(n); i++ {
			if 4 > size-offset {
				return nil, errShortErr
			}
			klen := int(be.Uint32(buf[offset : offset+4]))
			offset += 4

			if klen > size-offset {
				return nil, errShortErr
			}
			key := string(buf[offset : offset+klen])
			offset += klen

			if 4 > size-offset {
				return nil, errShortErr
			}
			nv := be.Uint32(buf[offset : offset+4])
			offset += 4

			value := make([]string, 0)
			for y := 0; y < int(nv); y++ {
				if 4 > size-offset {
					return nil, errShortErr
				}
				vlen := int(be.Uint32(buf[offset : offset+4]))
				offset += 4

				if vlen > size-offset {
					return nil, errShortErr
				}
				v := string(buf[offset : offset+vlen])
				offset += vlen
				value = append(value, v)
			}
			frg.RespHeader[key] = value
		}
	}

	if 4 > size-offset {
		return frg, nil
	}
	no = be.Uint32(buf[offset : offset+4])
	if no == 3 {
		offset += 4

		if 4 > size-offset {
			return nil, errShortErr
		}
		blen := int(be.Uint32(buf[offset : offset+4]))
		offset += 4

		if blen > size-offset {
			return nil, errShortErr
		}
		body := buf[offset : offset+blen]
		offset += blen
		frg.Body = body
	}

	if len(buf) != offset {
		return nil, errFormatErr
	}

	return frg, nil
}
